﻿using System.Diagnostics.CodeAnalysis;

namespace DimensionsHelper.LanguageService.Protocol;

/// <summary>
///     The diagnostic severity.
/// </summary>
public enum DiagnosticSeverity
{
    /// <summary>
    ///     Reports an error.
    /// </summary>
    Error = 1,

    /// <summary>
    ///     Reports a warning.
    /// </summary>
    Warning = 2,

    /// <summary>
    ///     Reports an information.
    /// </summary>
    Information = 3,

    /// <summary>
    ///     Reports a hint.
    /// </summary>
    Hint = 4
}


/// <summary>
///     The diagnostic tags.
/// </summary>
public enum DiagnosticTag
{
    /// <summary>
    ///     Unused or unnecessary code.
    /// </summary>
    /// <remarks>
    ///     Clients are allowed to render diagnostics with this tag faded out instead of having
    ///     an error squiggle.
    /// </remarks>
    Unnecessary = 1,


    /// <summary>
    ///     Deprecated or obsolete code.
    /// </summary>
    /// <remarks>
    ///     Clients are allowed to rendered diagnostics with this tag strike through.
    /// </remarks>
    Deprecated = 2
}


public readonly struct DiagnosticCode
{
    public DiagnosticCode(long code)
    {
        LongCode = code;
    }


    public DiagnosticCode(string code)
    {
        StringCode = code;
    }


    public long LongCode { get; }


    public bool IsLong => StringCode == null;


    public string? StringCode { get; }


    [MemberNotNullWhen(true, nameof(StringCode))]
    public bool IsString => StringCode != null;


    public static implicit operator DiagnosticCode(long value)
    {
        return new DiagnosticCode(value);
    }


    public static implicit operator DiagnosticCode(string value)
    {
        return new DiagnosticCode(value);
    }


    public static implicit operator DiagnosticCode(int value)
    {
        return new DiagnosticCode(value);
    }


    public static implicit operator long(DiagnosticCode code)
    {
        return code.IsLong ? code.LongCode : 0;
    }


    public static implicit operator string?(DiagnosticCode code)
    {
        return code.IsString ? code.StringCode : null;
    }
}


/// <summary>
///     structure to capture a description for an error code.
/// </summary>
/// <param name="Href">A URI to open with more information about the diagnostic error.</param>
public record CodeDescription(Uri Href);


/// <summary>
///     Represents a diagnostic, such as a compiler error or warning. Diagnostic objects
///     are only valid in the scope of a resource.
/// </summary>
public record Diagnostic
{
    /// <summary>
    ///     The diagnostic's severity. Can be omitted. If omitted it is up to the
    ///     client to interpret diagnostics as error, warning, info or hint.
    /// </summary>
    public required DiagnosticSeverity Severity { get; init; }


    /// <summary>
    ///     The range at which the message applies
    /// </summary>
    public required Range Range { get; init; }


    /// <summary>
    ///     The diagnostic's code, which might appear in the user interface.
    /// </summary>
    public DiagnosticCode? Code { get; init; }


    /// <summary>
    ///     An optional property to describe the error code.
    ///     Requires the code field (above) to be present/not null.
    /// </summary>
    public CodeDescription? CodeDescription { get; init; }


    /// <summary>
    ///     A human-readable string describing the source of this
    ///     diagnostic, e.g. 'typescript' or 'super lint'. It usually
    ///     appears in the user interface.
    /// </summary>
    public string? Source { get; init; }


    /// <summary>
    ///     The diagnostic's message. It usually appears in the user interface
    /// </summary>
    public required string Message { get; init; }


    /// <summary>
    ///     Additional metadata about the diagnostic.
    /// </summary>
    public DiagnosticTag[]? Tags { get; init; }


    /// <summary>
    ///     An array of related diagnostic information, e.g. when symbol-names within
    ///     a scope collide all definitions can be marked via this property.
    /// </summary>
    public DiagnosticRelatedInformation[]? RelatedInformation { get; init; }
}


/// <summary>
///     Represents a related message and source code location for a diagnostic. This should be
///     used to point to code locations that cause or related to a diagnostics, e.g. when duplicating
///     a symbol in a scope.
/// </summary>
public record DiagnosticRelatedInformation
{
    /// <summary>
    ///     The location of this related diagnostic information.
    /// </summary>
    public required Location Location { get; init; }

    /// <summary>
    ///     The message of this related diagnostic information.
    /// </summary>
    public required string Message { get; init; }
}